/**
 * 
 */
package com.gragra.sampling;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import java.io.BufferedWriter;
import java.io.IOException;
import com.gragra.data.MixingCorpus;
import com.gragra.sampling.annealingSchedules.LinearAnnealingFromMaxToMin;
/**
 * This class is used to sample with a number of parallel realizations of a sampling process, it is assumed
 * that the parallel samples might interact, therefore different sections of the assignments are sampled by the
 * parallel threads. In order to enable this the data is split into section. note that it is assumed that the data
 * is prepared for the parallel sampling with multiple threads. this also implements type based sampling that
 * is used after a certain number of steps
 * @author Christoph Teichmann
 * created Feb 2, 2013 5:46:44 PM
 * @version 0.1
 */
public class RunSamplingPopulation
{
	/**
	 * 
	 */
	private double best = Double.NEGATIVE_INFINITY;
	/**
	 * 
	 */
	private final MixingCorpus<? extends PopulationSamplingStructure> data;
	/**
	 * 
	 */
	private int threads =  3;
	/**
	 * @param threads the number of threads that will be used
	 */
	public void setThreads(int threads)
	{this.threads = threads;}
	/**
	 * @param ann the annealing schedule that will be used
	 */
	public void setAnn(Annealing ann)
	{this.ann = ann;}
	/**
	 * @param data
	 */
	public RunSamplingPopulation(
			MixingCorpus<? extends PopulationSamplingStructure> data)
	{
		super();
		this.data = data;
	}
	/**
	 * 
	 */
	private Annealing ann = new LinearAnnealingFromMaxToMin(1.0, 1.0, 0.0);
	/**
	 * first entry in returned array contains the best value seen so far, second entry contains the number of function evaluations
	 * @param pass
	 * @throws IOException, InterruptedException 
	 */
	public DoubleArrayList[] run(PopulationProbabilityAssigner pass, int burnIn, int samples, int skip) throws IOException, InterruptedException
	{
		this.threads = pass.getPopulationSize();
		tua = new ThreadedUniformAccess(threads);
		int p = 0;
		DoubleArrayList l = new DoubleArrayList();
		DoubleArrayList fE = new DoubleArrayList();
		DoubleArrayList aE = new DoubleArrayList();
		for(int i=0;i<data.size();++i)
		{
			PopulationSamplingStructure pss = data.getEntry(i);
			pss.initialize(pass, tua);
			for(int thread=0;thread<threads;++thread)
			{pass.add(pss, thread);}
		}
		for(int i=0;i<burnIn;++i)
		{
			double anneal = this.ann.getBurnInAnnealing(p++);
			pass.reduceStructuralAnnealing(p);
			makePass(pass, p, l, fE, aE, anneal);
		}
		int seen = 0;
		for(int i=0;true;++i)
		{
			double anneal = this.ann.getSamplingAnnealing(p++);
			pass.reduceStructuralAnnealing(p);
			makePass(pass, p, l, fE, aE, anneal);
			if(i%skip==0)
			{
				addSample();
				++seen;
				if(seen >= samples)
				{break;}
			}
			
		}
		return new DoubleArrayList[] {l,fE,aE};
	}
	/**
	 * @param pass
	 * @param p
	 * @param l
	 * @param fE 
	 * @param aE 
	 * @param anneal
	 * @throws InterruptedException
	 * @throws IOException
	 */
	protected void makePass(PopulationProbabilityAssigner pass, int p,
			DoubleArrayList l, DoubleArrayList fE, DoubleArrayList aE, double anneal) throws InterruptedException,
			IOException
	{
		this.sample(pass,anneal);
		makeBest(pass,l,fE,aE,p);
	}
	/**
	 * 
	 */
	private ThreadedUniformAccess tua;
	/**
	 * 
	 */
	private void addSample()
	{
		for(int i=0;i<this.data.size();++i)
		{this.data.getEntry(i).addSample();}
		System.out.println("added sample");
	}
	/**
	 * 
	 * @author Christoph Teichmann
	 * 30.09.2013
	 */
	private class BestThread extends Thread
	{
		/**
		 * 
		 */
		private final PopulationProbabilityAssigner ppa;
		/**
		 * @param ppa
		 * @param thread
		 */
		public BestThread(PopulationProbabilityAssigner ppa, int thread)
		{
			super();
			this.ppa = ppa;
			this.thread = thread;
		}
		/**
		 * 
		 */
		private  double  prob = 0.0;
		/**
		 * @return the prob
		 */
		public double getProb()
		{return this.prob;}
		/**
		 * 
		 */
		private final int thread;
		/* (non-Javadoc)
		 * @see java.lang.Thread#run()
		 */
		@Override
		public void run()
		{
			prob = ppa.getNonLocalLogLike(thread);
			for(int i=0;i<data.size();++i)
			{prob += ppa.getLocalLogLike(data.getEntry(i), thread);}
		}
	}
	/**
	 * 
	 * @param pass
	 * @param fE 
	 * @param aE 
	 * @return
	 * @throws InterruptedException 
	 * @throws IOException 
	 */
	private void makeBest(PopulationProbabilityAssigner pass, DoubleArrayList l, DoubleArrayList fE, DoubleArrayList aE, int p) throws InterruptedException, IOException
	{
		BestThread[] ts = new BestThread[threads];
		for(int i=0;i<threads;++i)
		{
			ts[i] = new BestThread(pass, i);
			ts[i].start();
		}
		double d = Double.NEGATIVE_INFINITY;
		int best = -1;
		for(int i = 0; i < ts.length; i++)
		{
			BestThread thread = ts[i];
			thread.join();
			if(d < thread.getProb())
			{
				best = i;
				d = thread.getProb();
			}
		}
		if(this.best<d)
		{
			this.best = d;
			for(int i=0;i<this.data.size();++i)
			{this.data.getEntry(i).setBest(best);}
		}
		System.out.println("best seen this round");
		System.out.println(d);
		System.out.println("Overall best value is:");
		System.out.println(this.best);
		l.add(-this.best);
		fE.add(pass.getNumberOfEvaluations());
		aE.add(pass.getNumberOfAuxEvals());
	}
	/**
	 * 
	 * @param pass
	 * @param anneal
	 * @throws InterruptedException 
	 */
	private void sample(PopulationProbabilityAssigner pass, double anneal) throws InterruptedException
	{
		Thread[] ts = new Thread[threads];
		for(int i=0;i<threads;++i)
		{
			ts[i] = new SamplingThread( i, anneal, pass);
			ts[i].start();
		}
		for(int i = 0; i < ts.length; i++)
		{ts[i].join();}
	}
	/**
	 * 
	 * @author Christoph Teichmann
	 * 30.09.2013
	 */
	private class SamplingThread extends Thread
	{
		/**
		 * 
		 */
		private final int thread;
		/**
		 * @param forward
		 * @param thread
		 * @param annealing
		 * @param pass
		 */
		public SamplingThread( int thread, double annealing,
				PopulationProbabilityAssigner pass)
		{
			super();
			this.thread = thread;
			this.annealing = annealing;
			this.pass = pass;
		}
		/* (non-Javadoc)
		 * @see java.lang.Thread#run()
		 */
		@Override
		public void run()
		{
			for(int i=0;i<data.size();++i)
			{sampleEntry(i);}
		}
		/**
		 * @param i
		 */
		protected void sampleEntry(int position)
		{
			PopulationSamplingStructure pss = data.getEntry(position);
			synchronized(pss)
			{
				pass.remove(pss, thread);
				pss.resample(pass, annealing, thread, tua);
				pass.add(pss, thread);
			}
		}
		/**
		 * 
		 */
		private final double annealing;
		/**
		 * 
		 */
		private final PopulationProbabilityAssigner pass;
	}
	/**
	 * 
	 * @param sampledOutput
	 * @throws IOException 
	 */
	public void writeOut(BufferedWriter sampledOutput) throws IOException
	{
		for(int i=0;i<this.data.size();++i)
		{
			if(i!=0)
			{sampledOutput.newLine();}
			sampledOutput.write(data.getEntry(i).minRisk(null));
		}
		sampledOutput.flush();
	}
	/**
	 * 
	 * @param bestOutput
	 * @throws IOException 
	 */
	public void writeOutBest(BufferedWriter bestOutput) throws IOException
	{
		for(int i=0;i<this.data.size();++i)
		{
			if(i!=0)
			{bestOutput.newLine();}
			bestOutput.write(data.getEntry(i).getBest(null));
		}
		bestOutput.flush();
	}
}